package com.innovate.nms.service.channel;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.boot.context.embedded.PortInUseException;
import org.springframework.stereotype.Component;

import com.google.common.base.Strings;
import com.google.common.eventbus.Subscribe;
import com.innovate.nms.domain.SmsConfig;
import com.ivan.toolkit.common.Events;
import com.jamierf.rxtx.RXTXLoader;

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import lombok.extern.log4j.Log4j;

@Component
@Log4j
public class SmsChannel extends Channel implements SerialPortEventListener, Runnable {
	
	//private static String pattern1 = "\\d{11,},@\\d{3}>[^@!]+!";
	//private static String pattern2 = "@\\d{3}>[^@!]+!";
	
	Thread thread = null;
	
	CommPort port = null;
	
	InputStream in = null;
    OutputStream out = null;

	private String text = "";
	
	private long latest = System.currentTimeMillis();
	
	byte[] buffer = new byte[512];
	
    
	public SmsChannel() {
		super("SMS");
	}

	@PostConstruct
	public void start() {
		
		try {
			RXTXLoader.load();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		Events.register(this);
		
		thread = new Thread(this);
		thread.setName("SMS PROVIDER");
		thread.setDaemon(true);
		thread.start();
		
	}

	@PreDestroy
	public void stop() {
		try {
		this.closePort();
		} catch (Exception e) {}
	}
	
	@Subscribe
	public void onEvent(Events event) {
		
		if(event.getKey() instanceof SmsConfig) {
			try {
				this.closePort();
			} catch (Exception e) {}
		}
	}

	@Override
	public int getTimeout() {
		SmsConfig config = SmsConfig.instance.get();
		return config.getTimeout();
	}

	public void init() throws Exception {
		
		SmsConfig config = SmsConfig.instance.get();
		
		boolean autoDetect = config.isAutoDetect();
		
		String portName = config.getPort();
		
		int baudrate = config.getBaudrate();
		
		int databits = config.getDatabits();
		if(databits < 5 || databits > 8) {
			databits = 8;
		}
		
		int stopbits = config.getStopbits();
		if(stopbits < 1 || stopbits > 3) {
			stopbits = 1;
		}
		
		int parity = config.getParity();
		if(parity < 0 || parity > 4) {
			parity = 0;
		}

		try {
			
			if(autoDetect) {
				Enumeration<?> e = CommPortIdentifier.getPortIdentifiers();
				if(e.hasMoreElements()) {
					CommPortIdentifier portId = (CommPortIdentifier)e.nextElement();
					port = portId.open(this.getClass().getName(), 2000);
					log.info("SerialPort auto detected. port=" + port.getName());
				}
				
			} else {
				CommPortIdentifier portId = CommPortIdentifier.getPortIdentifier(portName);
				port = portId.open(this.getClass().getName(), 2000);
				log.info("SerialPort Opend. port=" + port.getName());
			}
			
			if(port instanceof SerialPort) {
				
				SerialPort serialPort = (SerialPort)port;
				
				serialPort.setSerialPortParams(baudrate, databits, stopbits, parity);
				
				in = serialPort.getInputStream();
			    out = serialPort.getOutputStream();
			    
				serialPort.addEventListener(this);
				serialPort.notifyOnDataAvailable(true);
				
			}
			
			
		} catch (NoSuchPortException e) {

			throw new RuntimeException("未找到端口： " + portName);
			
		} catch (PortInUseException e) {
			
			throw new RuntimeException("端口被占用： " + portName);
			
		}
		
	}

	void closePort() {
		if(port != null) {
			port.close();
			port = null;
			in = null;
			out = null;
		}
	}
	
	@Override
	public void run() {
		
		while(true) {
			
			if(port == null) {
				try {
					init();
				} catch(Exception e) {
					
				}
			}
			
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				break;
			}
			
		}
		
	}

	@Override
	public void serialEvent(SerialPortEvent e) {
		int eventType = e.getEventType();	
		switch(eventType) {	
		case SerialPortEvent.DATA_AVAILABLE:
			try {
				read(in);		
			} catch (IOException e1) {
				closePort();
			}
			break;
		default:
			log.info("SerialPortEvent with type:" + eventType);
			break;
		
		}
	}
	
	public void read(InputStream in) throws IOException {
		
		SmsConfig config = SmsConfig.instance.get();

		// ������ݹ���
		long now = System.currentTimeMillis();
		if((now - latest) > config.getWaitInterval()) {
			text = "";
			log.info("TEXT EXPIRED, CLEAN");
		}
		latest = now;
		
		int reads = in.read(buffer);
		
		String s = new String(buffer, 0, reads);
		log.info("SMS READ -> " + s);
		
		text += s;
		log.info("PARSE: " + text);
		


		// 必要
		// 多个应答帧同时到达的情况是可能的（某些设备刚开机时，会处理以前收到的短信指令），
		// 为了防止这种情况对于正常应答的干扰，有必要对数据再做一次分割处理
		while(true) {
			
			int x = text.indexOf('!');
			if(x == -1) {
				System.out.println("No command found.");
				break;
			}
			
			String temp = text.substring(0, x + 1);
			System.out.println("PROCESS: " + temp);

			if(temp.matches(pattern1())) {
				String[] ss = temp.split(",", 2);
				if(ss.length == 2) {
					ChannelEvent pdu = new ChannelEvent(this, ss[0], ss[1]);
					log.info("SMS RESPONSE: source=" + ss[0] + " content=" + ss[1]);
					eventbus.post(pdu);
				}
				//text = "";
			} else if(temp.matches(pattern2())) {
				ChannelEvent pdu = new ChannelEvent(this, null, temp);
				log.info("SMS RESPONSE: source=NULL content=" + temp);
				eventbus.post(pdu);
				//text = "";
			} else {
				log.info("PROCESS FAILED!");
			}
			
			text = text.substring(x + 1, text.length());
		
		}
		
	}
	
	private String pattern1() {
		SmsConfig config = SmsConfig.instance.get();
		return String.format("\\d{11,},%s\\d{3}>[^@!]+!", config.getLeader());
	}
	
	private String pattern2() {
		SmsConfig config = SmsConfig.instance.get();
		return String.format("%s\\d{3}>[^@!]+!", config.getLeader());
	}

	@Override
	public boolean isReady() {
		return port != null;
	}

	@Override
	public synchronized void send(String target, String content) throws Exception {
		
		if(port == null) 
			throw new RuntimeException("串口未准备就绪！");
		
		if(Strings.isNullOrEmpty(target)) 
			throw new RuntimeException("目标未指定！");
		
		if(Strings.isNullOrEmpty(content)) 
			throw new RuntimeException("内容未指定！");
		
		log.info("SMS REQUEST: target=" + target + " content=" + content);
		
		ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
		DataOutputStream dos = new DataOutputStream(baos);
		
		SmsConfig config = SmsConfig.instance.get();
		
		if(config.getProtocol() == 0) {
			
			// F2003
			dos.writeByte(0x02);
			dos.writeByte(target.length());
			dos.writeBytes(target);
			dos.writeShort(content.length());
			dos.writeBytes(content);
			dos.writeByte(0x03);
		
		} else {
		
		 	// JYC331
			dos.writeBytes(target);
			dos.writeBytes(":");
			dos.writeBytes("0");
			dos.writeBytes(":");
			dos.writeBytes(content);
		
		}
		
		out.write(baos.toByteArray());
		
	}

	
	public static List<String> getPortNames() {
		List<String> list = new LinkedList<String>();
		Enumeration<?> pis = CommPortIdentifier.getPortIdentifiers();
		while(pis.hasMoreElements()) {
			CommPortIdentifier pi = (CommPortIdentifier)pis.nextElement();
			list.add(pi.getName());
		}
		return list;
	}

}
